package com.xhj.mybatis.scripting.xmltags;

import com.xhj.mybatis.builder.BaseBuilder;
import com.xhj.mybatis.mapping.SqlSource;
import com.xhj.mybatis.scripting.defaults.RawSqlSource;
import com.xhj.mybatis.session.Configuration;
import org.dom4j.Element;
import org.w3c.dom.Node;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * XMLScriptBuilder
 *
 * @author XJks
 * @description XML 脚本构建器，用于解析 MyBatis 映射文件中的 SQL 语句节点，生成 SqlSource 对象。
 */
public class XMLScriptBuilder extends BaseBuilder {
    /**
     * 当前解析的 XML 节点（SQL 脚本的根节点），即 <select>、<insert> 、<update> 或 <delete> 节点。
     */
    private Element context;

    /**
     * 标记 SQL 是否为动态 SQL（包含条件<if>、循环<foreach>等标签）
     */
    private boolean isDynamic;

    /**
     * 参数类型（用于 SQL 绑定参数类型）
     */
    private Class<?> parameterType;

    /**
     * 存储各类 XML 节点对应的处理器
     */
    private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();

    /**
     * 构造方法，参数为配置和 XML 节点
     *
     * @param configuration MyBatis 配置对象
     * @param context       XML 元素节点
     */
    public XMLScriptBuilder(Configuration configuration, Element context) {
        this(configuration, context, null);
    }

    /**
     * 构造方法，参数为配置、XML 节点和参数类型
     *
     * @param configuration MyBatis 配置对象
     * @param context       XML 元素节点
     * @param parameterType 参数类型
     */
    public XMLScriptBuilder(Configuration configuration, Element context, Class<?> parameterType) {
        super(configuration);
        this.context = context;
        this.parameterType = parameterType;
        // 初始化节点处理器映射
        initNodeHandlerMap();
    }

    /**
     * 初始化节点处理器映射，将 XML 标签名与对应处理器关联
     */
    private void initNodeHandlerMap() {
        // 添加处理 <trim> 标签的处理器
        nodeHandlerMap.put("trim", new TrimHandler());
        // 添加处理 <where> 标签的处理器
        nodeHandlerMap.put("where", new WhereHandler());
        // 添加处理 <set> 标签的处理器
        nodeHandlerMap.put("set", new SetHandler());
        // 添加处理 <foreach> 标签的处理器
        nodeHandlerMap.put("foreach", new ForEachHandler());
        // 添加处理 <if> 标签的处理器
        nodeHandlerMap.put("if", new IfHandler());
        // 添加处理 <choose> 标签的处理器
        nodeHandlerMap.put("choose", new ChooseHandler());
        // 添加处理 <when> 标签的处理器，与 <if> 标签共用 IfHandler
        nodeHandlerMap.put("when", new IfHandler());
        // 添加处理 <otherwise> 标签的处理器
        nodeHandlerMap.put("otherwise", new OtherwiseHandler());
        // 添加处理 <bind> 标签的处理器
        nodeHandlerMap.put("bind", new BindHandler());
    }

    /**
     * 解析脚本节点，生成 SqlSource 对象。
     *
     * @return 解析后的 SqlSource 对象
     */
    public SqlSource parseScriptNode() {
        // 解析动态标签，生成 MixedSqlNode
        MixedSqlNode rootSqlNode = parseDynamicTags(context);
        // 声明一个返回的 SqlSource 变量
        SqlSource sqlSource;
        // 根据 isDynamic 标志选择创建动态 SQL 还是静态 SQL
        if (isDynamic) {
            // 如果是动态 SQL，创建 DynamicSqlSource
            sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
        } else {
            // 如果是静态 SQL，创建 RawSqlSource
            sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
        }
        return sqlSource;
    }

    /**
     * 从 <select>、<insert>、<update> 或 <delete> 根节点中解析动态标签，生成 MixedSqlNode。
     *
     * @param node XML 元素节点
     * @return 解析后的 MixedSqlNode，包含多个 SqlNode 子节点
     */
    protected MixedSqlNode parseDynamicTags(Element node) {
        // 存储解析结果的 SqlNode 列表
        List<SqlNode> contents = new ArrayList<>();
        // 获取子节点，TODO:注意这里使用的是 dom4j 的 Node 类型，而不是 w3c.dom.Node
        List<org.dom4j.Node> children = node.content();
        // 遍历每个子节点
        for (org.dom4j.Node child : children) {
            /**
             * TODO:
             *  1.条件判断细节:
             *   1.1 疑问:在节点类型判断中，对应对象类型是 org.dom4j.Node，而对应常量是 w3c.dom.Node 中的常量，这可能是否存在一些不妥？
             *   1.2 回答:
             *          虽然 org.dom4j.Node 和 w3c.dom.Node 是不同的类，但它们在节点类型的定义上是兼容的。dom4j 设计时参考了 w3c DOM 规范，因此它们使用相同的整数常量来表示节点类型（如 ELEMENT_NODE、TEXT_NODE、CDATA_SECTION_NODE 等）。这使得在判断节点类型时，可以直接使用 w3c.dom.Node 中定义的常量，而不需要额外转换或映射。这种设计简化了代码逻辑，使得在处理 XML 节点时更加方便和直观。
             *          在 Mybatis 源码中，也是这样使用的，直接用 w3c.dom.Node 的常量来判断 dom4j.Node 的类型。
             *  2.根据节点类型进行不同处理。
             *  3.如果是文本节点或 CDATA 节点，处理为文本 SQL 节点。
             *  4.如果是元素节点，交给对应的处理器处理成对应 SqlNode。
             *  5.分析 if-else 逻辑:
             *      5.1 解析后的 SqlNode 节点都会添加到 contents 列表中，最后返回 MixedSqlNode，这是一个树形结构的根节点，包含多个 SqlNode 子节点。
             *      5.2 该 if 分支处理后的 sqlNode 是树形结构中的叶子节点（TextSqlNode 或 StaticTextSqlNode），不会再包含子节点。
             *      5.3 该 else if 分支处理后的 sqlNode 可能是树形结构中的中间节点（如 IfSqlNode、ForEachSqlNode 等），这些节点会包含子节点，子节点会递归调用该方法进行解析。
             */
            if (child.getNodeType() == Node.CDATA_SECTION_NODE || child.getNodeType() == Node.TEXT_NODE) {
                // 如果是文本或 CDATA 节点，获取文本内容
                String data = child.getText();
                // 创建文本 SQL 节点
                TextSqlNode textSqlNode = new TextSqlNode(data);
                // 判断是否包含动态内容
                if (textSqlNode.isDynamic()) {
                    // 如果是动态文本 SQL，将文本 SQL 节点添加到返回结果列表中，并标记为动态 SQL
                    contents.add(textSqlNode);
                    isDynamic = true;
                } else {
                    // 如果不是动态文本 SQL，创建 StaticTextSqlNode，表示静态文本 SQL，并添加到返回结果列表中
                    contents.add(new StaticTextSqlNode(data));
                }
            } else if (child.getNodeType() == Node.ELEMENT_NODE) {
                /**
                 * TODO:
                 *  1.如果是元素节点，则根据标签名获取对应的处理器
                 *  2.元素节点指的是 XML 标签节点，比如 {@code <if>、<foreach>} 这种带有 {@code <></>} 符号的节点。
                 */
                String nodeName = child.getName();
                NodeHandler handler = nodeHandlerMap.get(nodeName);
                // 如果没有找到对应的处理器，抛出异常
                if (handler == null) {
                    throw new RuntimeException("Unknown element <" + nodeName + "> in SQL statement.");
                }
                // 调用处理器处理节点，TODO:因为该分支的条件 child.getNodeType() == Node.ELEMENT_NODE 已经保证了 child 是元素节点，这里强制转换为 Element。
                handler.handleNode((Element) child, contents);
                /**
                 * TODO:
                 *  1.因为处理器处理的节点都是动态标签，所以这里直接将 isDynamic 标记为 true。
                 *  2.在 {@link TextSqlNode} 中，动态 SQL 语句含义:通过判断文本中是否包含 ${} 占位符，表示 SQL 片段会被参数直接拼接，内容可能变化，但结构一般不变。
                 *  3.在该方法 {@link #parseDynamicTags(Element)} 这一分支中，动态 SQL 语句含义:处理的节点都是动态标签，比如 {@code <if>、<foreach>} 这种标签，只要有 XML 标签，就认为 SQL 结构可能变化，比如条件拼接、循环生成等，SQL 语句的结构和内容都可能发生变化。
                 */
                isDynamic = true;
            }
        }
        // 对结果列表进行封装，返回 MixedSqlNode
        return new MixedSqlNode(contents);
    }

    /**
     * 节点处理器接口，所有处理器都实现此接口
     */
    private interface NodeHandler {
        /**
         * 处理指定的 XML 节点，并将结果添加到目标内容列表中。
         *
         * @param nodeToHandle   待处理的 XML 节点
         * @param targetContents 处理结果添加到的目标内容列表
         */
        void handleNode(Element nodeToHandle, List<SqlNode> targetContents);
    }

    /**
     * 处理 <bind> 标签，变量绑定
     */
    private class BindHandler implements NodeHandler {
        public BindHandler() {
            // 防止合成访问
        }

        @Override
        public void handleNode(Element nodeToHandle, List<SqlNode> targetContents) {
            // 获取变量名和
            final String name = nodeToHandle.attributeValue("name");
            // 获取变量表达式
            final String expression = nodeToHandle.attributeValue("value");
            // 创建变量声明节点
            final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
            // 添加到结果
            targetContents.add(node);
        }
    }

    /**
     * 处理 <trim> 标签，去除 SQL 前后多余字符
     */
    private class TrimHandler implements NodeHandler {
        public TrimHandler() {
            // 防止合成访问
        }

        @Override
        public void handleNode(Element nodeToHandle, List<SqlNode> targetContents) {
            // 递归解析子节点，TODO:注意这里返回的是 MixedSqlNode，是多个 SqlNode 的组合。
            MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
            // 获取前缀
            String prefix = nodeToHandle.attributeValue("prefix");
            // 获取前缀覆盖
            String prefixOverrides = nodeToHandle.attributeValue("prefixOverrides");
            // 获取后缀
            String suffix = nodeToHandle.attributeValue("suffix");
            // 获取后缀覆盖
            String suffixOverrides = nodeToHandle.attributeValue("suffixOverrides");
            // 创建 Trim 节点
            TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
            // 添加到结果
            targetContents.add(trim);
        }
    }

    /**
     * 处理 <where> 标签，自动添加 WHERE 关键字
     */
    private class WhereHandler implements NodeHandler {
        public WhereHandler() {
            // 防止合成访问
        }

        @Override
        public void handleNode(Element nodeToHandle, List<SqlNode> targetContents) {
            // 递归解析子节点
            MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
            // 创建 Where 节点
            WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
            // 添加到结果
            targetContents.add(where);
        }
    }

    // 处理 <set> 标签，自动添加 SET 关键字
    private class SetHandler implements NodeHandler {
        public SetHandler() {
            // 防止合成访问
        }

        @Override
        public void handleNode(Element nodeToHandle, List<SqlNode> targetContents) {
            // 递归解析子节点
            MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
            // 创建 Set 节点
            SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
            // 添加到结果
            targetContents.add(set);
        }
    }

    /**
     * 处理 <foreach> 标签，循环生成 SQL 片段
     */
    private class ForEachHandler implements NodeHandler {
        public ForEachHandler() {
            // 防止合成访问
        }

        @Override
        public void handleNode(Element nodeToHandle, List<SqlNode> targetContents) {
            // 递归解析子节点
            MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
            // 获取集合名
            String collection = nodeToHandle.attributeValue("collection");
            // 获取循环变量名
            String item = nodeToHandle.attributeValue("item");
            // 获取索引变量名
            String index = nodeToHandle.attributeValue("index");
            // 获取循环开始符
            String open = nodeToHandle.attributeValue("open");
            // 获取循环结束符
            String close = nodeToHandle.attributeValue("close");
            // 获取分隔符
            String separator = nodeToHandle.attributeValue("separator");
            // 创建 ForEach 节点
            ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);
            // 添加到结果
            targetContents.add(forEachSqlNode);
        }
    }

    /**
     * 处理 <if> 和 <when> 标签，条件判断
     */
    private class IfHandler implements NodeHandler {
        public IfHandler() {
            // 防止合成访问
        }

        @Override
        public void handleNode(Element nodeToHandle, List<SqlNode> targetContents) {
            // 递归解析子节点
            MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
            // 获取条件表达式
            String test = nodeToHandle.attributeValue("test");
            // 创建 If 节点
            IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
            // 添加到结果
            targetContents.add(ifSqlNode);
        }
    }

    /**
     * 处理 <otherwise> 标签，choose 的默认分支
     */
    private class OtherwiseHandler implements NodeHandler {
        public OtherwiseHandler() {
            // 防止合成访问
        }

        @Override
        public void handleNode(Element nodeToHandle, List<SqlNode> targetContents) {
            // 递归解析子节点
            MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
            // 直接添加到结果
            targetContents.add(mixedSqlNode);
        }
    }

    /**
     * 处理 <choose> 标签，类似 switch-case
     */
    private class ChooseHandler implements NodeHandler {
        public ChooseHandler() {
            // 防止合成访问
        }

        @Override
        public void handleNode(Element nodeToHandle, List<SqlNode> targetContents) {
            // 存储 when 节点
            List<SqlNode> whenSqlNodes = new ArrayList<>();
            // 存储 otherwise 节点
            List<SqlNode> otherwiseSqlNodes = new ArrayList<>();
            // 处理子节点
            handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
            // 获取默认分支
            SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
            // 创建 Choose 节点
            ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
            // 添加到结果
            targetContents.add(chooseSqlNode);
        }

        /**
         * 处理 <choose> 标签下的 <when> 和 <otherwise> 子节点
         *
         * @param chooseSqlNode   choose 节点
         * @param ifSqlNodes      存储所有 <when> 标签解析得到的 SqlNode
         * @param defaultSqlNodes 存储所有 <otherwise> 标签解析得到的 SqlNode
         */
        private void handleWhenOtherwiseNodes(Element chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) {
            // 获取 <choose> 节点的所有子节点（可能是 <when> 或 <otherwise>）
            List<Element> children = chooseSqlNode.elements();
            // 遍历每一个子节点
            for (Element child : children) {
                // 获取当前子节点的标签名（如 "when" 或 "otherwise"）
                String nodeName = child.getName();
                // 根据标签名从 nodeHandlerMap 中获取对应的处理器
                NodeHandler handler = nodeHandlerMap.get(nodeName);
                // 根据标签类型调用相应处理器
                if (handler instanceof IfHandler) {
                    // 如果处理器是 IfHandler（用于处理 <when> 或 <if> 标签），调用处理器，将解析结果添加到 ifSqlNodes 列表
                    handler.handleNode(child, ifSqlNodes);
                } else if (handler instanceof OtherwiseHandler) {
                    // 如果处理器是 OtherwiseHandler（用于处理 <otherwise> 标签），调用处理器，将解析结果添加到 defaultSqlNodes 列表
                    handler.handleNode(child, defaultSqlNodes);
                }
                // 其他标签不会处理（只处理 when/if 和 otherwise）
            }
        }

        /**
         * 获取 choose 节点的默认分支（即 <otherwise> 标签对应的 SqlNode）
         *
         * @param defaultSqlNodes 存储所有 <otherwise> 标签解析得到的 SqlNode，理论上只能有一个
         * @return 默认分支的 SqlNode，如果没有则返回 null
         */
        private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
            // 默认分支，初始为 null
            SqlNode defaultSqlNode = null;
            // 根据 otherwise 节点的数量决定默认分支
            if (defaultSqlNodes.size() == 1) {
                // 如果只有一个 <otherwise> 节点，直接取出作为默认分支
                defaultSqlNode = defaultSqlNodes.get(0);
            } else if (defaultSqlNodes.size() > 1) {
                // 如果有多个 <otherwise> 节点，抛出异常，choose 语句只能有一个默认分支
                throw new RuntimeException("Too many default (otherwise) elements in choose statement."); // 默认分支只能有一个
            }
            // 返回默认分支（可能为 null）
            return defaultSqlNode;
        }
    }
}
